package com.fcat.base.smartapi.service.impl;

import com.fcat.base.smartapi.lib.*;
import com.fcat.base.smartapi.service.TokenService;
import com.fcat.base.smartapi.util.TokenUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigInteger;
import java.util.Random;

public class TokenServiceImpl implements TokenService {
    private static final Logger LOGGER = LoggerFactory.getLogger(TokenServiceImpl.class);

    private String keyType = "2";
    private String nKeyType = "2";
//    private String vendingKey = "5555555555555555";
    private String vendingKey = "abababababababab";
    private String nVendingKey = "5555555555555555";
    private int maxSeqNo = 230;
    private int maxTokenNum = 25;
    private int maxMonthNum = 6;
    private int decimalOffset = 5;
    private int NodecimalOffset = 80;

    private String getMeterKeys(String meterNo, String sgc, int tariffIndex, int keyVersion) {
        MeterKeys meterKeys = new MeterKeys(meterNo, this.vendingKey);
        meterKeys.setTI(tariffIndex);
        meterKeys.setKRN(String.valueOf(keyVersion));
        meterKeys.setSGC(sgc);
        try {
            meterKeys.DKGA2();
        } catch (Exception e) {
            e.printStackTrace();
        }

        String keys = meterKeys.getMeterKeyString();
        System.out.println("MeterKeys::  " + keys);
        return keys;
    }

    private String getMeterKeys(String sMeterNo, int iTariffIndex, String sSgc,
                                String sVendingKey, String sKeyType, int iKeyVersion) {
        MeterKeys meterKeys = new MeterKeys(sMeterNo, sVendingKey);
        meterKeys.setTI(iTariffIndex);
        meterKeys.setKRN(String.valueOf(iKeyVersion));
        meterKeys.setSGC(sSgc);
        meterKeys.setKT(sKeyType);
        try {
            meterKeys.DKGA2();
        } catch (Exception e) {
            e.printStackTrace();
        }

        String keys = meterKeys.getMeterKeyString();
        LOGGER.info("MeterKeys::  " + keys);
        return keys;
    }

    @Override
    public String getCreditToken(String meterNo, String sgc, int tariffIndex, int keyVersion, int keyExpiredTime, long keyNo, int seqNo, int amount) {
        int res = this.CheckInputParams(meterNo, tariffIndex, keyVersion, keyExpiredTime, keyNo, seqNo, amount);
        if (res != 0) {
            return this.getRtnErrorXML(res);
        } else {
            // 参数 meterNo, sgc, tariffIndex, keyVersion, 计算MeterKeys
            String strMeterKey = getMeterKeys(meterNo, sgc, tariffIndex, keyVersion);
//            System.err.println("MeterKeys: " + meterKeys.getMeterKeyString());
            // 参数 keyNo, seqNo, amount 计算token;  keyNo+seqNo 生成类似tid功能
            Random r = new Random();
            int rn = r.nextInt(16);     // 随机数0~15, 生产Token不同，但是不影响最终解码
//            LocalDateTime tid = LocalDateTime.now();
            //keyNo, seqNo  合并生成TID， keyNo使用16位，占用8~23位， seqNo使用8位，占用0~7位
            long tid = ((keyNo & 0xFFFF) << 8 | (seqNo & 0xFF));
            byte tsc = 0;   // Token 子类
            boolean mode = false;
            WorkType wt = WorkType.STA;
            // 参数 ta, tid, rn, byte tsc, String inkey, WorkType wt, boolean mode
            CreditToken myToken = new CreditToken(amount, tid, (byte) rn, tsc, strMeterKey, wt, mode);

            LOGGER.info("maskedTBoxToken1 -- " + myToken.StrToken);
            LOGGER.info("TCRC1 -- " + myToken.TCRC);
            LOGGER.info("FRandom -- " + myToken.RandomNum);
            LOGGER.info("FDateTime -- " + myToken.IssueTime);
            LOGGER.info(" TID  -- " + myToken.Tid);
            LOGGER.info("FAmount -- " + myToken.get_amount());

            return this.getRtnTokenXML(myToken.StrToken);
        }
    }

    @Override
    public String getKeyChangeToken(String meterNo, String sgc, int tariffIndex, int keyVersion, int keyExpiredTime, long keyNo, String nSgc, int nTariffIndex, int nKeyVersion, int nKeyExpiredTime, long nKeyNo) {
        String token = "";
        String tokenList = "";
        String decodeKey = "";
        String nDecodeKey = "";
        String nDecodeKeyBin = "";

        int res = this.CheckInputParams(meterNo, tariffIndex, keyVersion, keyExpiredTime, keyNo, 1, 1);
        if (res != 0) {
            return this.getRtnErrorXML(res);
        } else {
            res = this.CheckInputParams(meterNo, nTariffIndex, nKeyVersion, nKeyExpiredTime, nKeyNo, 1, 1);
            if (res != 0) {
                return this.getRtnErrorXML(res);
            } else {
                if (tariffIndex == 0 && keyVersion == 0 && keyExpiredTime == 0 && keyNo == 0L) {
//                    decodeKey = TokenUtils.getDecoderKey(meterNo, Integer.toString(tariffIndex), "000000", this.vendingKey, this.keyType, Integer.toString(keyVersion));
                    decodeKey = getMeterKeys(meterNo, tariffIndex, "000000", this.vendingKey, this.keyType, keyVersion);
                } else {
//                    decodeKey = TokenUtils.getDecoderKey(meterNo, Integer.toString(tariffIndex), sgc, this.vendingKey, this.keyType, Integer.toString(keyVersion));
                    decodeKey = getMeterKeys(meterNo, tariffIndex, sgc, this.vendingKey, this.keyType, keyVersion);
                }
//                nDecodeKey = TokenUtils.getDecoderKey(meterNo, Integer.toString(nTariffIndex), nSgc, this.nVendingKey, this.nKeyType, Integer.toString(nKeyVersion));
                nDecodeKey = getMeterKeys(meterNo, nTariffIndex, nSgc, this.nVendingKey, this.nKeyType, nKeyVersion);

                byte krn = (byte) nKeyVersion;
                byte ken = (byte) nKeyExpiredTime;
                byte ti = (byte) nTariffIndex;
                byte ro = 0;    // 仅能取值0 / 1
                LOGGER.info("nKeyType             : " + nKeyType);
                LOGGER.info("nKeyVersion     (KRN): " + krn);
                LOGGER.info("nKeyExpiredTime (KEN): " + ken);
                LOGGER.info("nTariffIndex    (TI) : " + ti);
                KeyChangeToken kct = new KeyChangeToken(Byte.valueOf(this.nKeyType), krn, ken, ti, ro, nDecodeKey, decodeKey,false);
                tokenList = kct.StrToken + ',' + kct.StrTokenKey2;
                return this.getRtnTokenXML(tokenList);
            }
        }
    }

    private String getClass2_ManageToken(int subClass, int data, String meterNo, String sgc, int tariffIndex, int keyVersion, int keyExpiredTime, long keyNo, int seqNo) {
        String decodeKey = "";
        int res = this.CheckInputParams(meterNo, tariffIndex, keyVersion, keyExpiredTime, keyNo, seqNo, 1);
        if (res != 0) {
            return this.getRtnErrorXML(res);
        } else {
            decodeKey = getMeterKeys(meterNo, tariffIndex, sgc, this.vendingKey, this.keyType, keyVersion);
            ManagementToken manageToken = new ManagementToken((long)(data & 0xFFFF), (byte)subClass, decodeKey);
            return this.getRtnTokenXML(manageToken.StrToken);
        }
    }

    @Override
    public String getClearBalanceToken(String meterNo, String sgc, int tariffIndex, int keyVersion, int keyExpiredTime, long keyNo, int seqNo) {
        return getClass2_ManageToken( 2, 0, meterNo, sgc, tariffIndex, keyVersion, keyExpiredTime, keyNo, seqNo);
    }

    @Override
    public String getClearEventToken(String meterNo, String sgc, int tariffIndex, int keyVersion, int keyExpiredTime, long keyNo, int seqNo) {
        return getClass2_ManageToken( 5, 0, meterNo, sgc, tariffIndex, keyVersion, keyExpiredTime, keyNo, seqNo);
    }

    @Override
    public String getMaxPowerLimitToken(String meterNo, String sgc, int tariffIndex, int keyVersion, int keyExpiredTime, long keyNo, int seqNo, int activatingModel, String activatingDate, int[] maxPowerLimits, int[] hours) {
        int maxPL =maxPowerLimits[0];
        return getClass2_ManageToken( 0, maxPL, meterNo, sgc, tariffIndex, keyVersion, keyExpiredTime, keyNo, seqNo);
    }

    @Override
    public String getSingleTariffToken(String meterNo, String sgc, int tariffIndex, int keyVersion, int keyExpiredTime, long keyNo, int seqNo, String activatingDate, int activatingModel, int validDate, int rate) {
//        token = "11";
        return null;
    }

    @Override
    public String getStepTariffToken(String meterNo, String sgc, int tariffIndex, int keyVersion, int keyExpiredTime, long keyNo, int seqNo, String activatingDate, int activatingModel, int validDate, int[] rates, int[] steps) {
//        token = "11";
        return null;
    }

    @Override
    public String getTOUTariffToken(String meterNo, String sgc, int tariffIndex, int keyVersion, int keyExpiredTime, long keyNo, int seqNo, String activatingDate, int activatingModel, int validDate, int[] rates, int[] times) {
//        token = "11";
        return null;
    }

    @Override
    public String getFriendModeToken(String meterNo, String sgc, int tariffIndex, int keyVersion, int keyExpiredTime, long keyNo, int seqNo, int friendMode, int[] times, int[] days, int N_of_allowable_days) {
//        token = "11";
        return null;
    }

    @Override
    public String getChangeMeterModeToken(String meterNo, String sgc, int tariffIndex, int keyVersion, int keyExpiredTime, long keyNo, int seqNo, int mode) {
//        token = "11";
        return null;
    }

    @Override
    public String getSetCreditAmountLimitOrOverdrawAmountLimitToken(String meterNo, String sgc, int tariffIndex, int keyVersion, int keyExpiredTime, long keyNo, int seqNo, int amountType, int amountLimit) {
//        token = "11";
        return null;
    }

    @Override
    public String SetLowCreditWarningLimitToken(String meterNo, String sgc, int tariffIndex, int keyVersion, int keyExpiredTime, long keyNo, int seqNo, int amountType, int amountLimit) {
//        token = "11";
        return null;
    }

    @Override
    public String getTestToken(int manufacturingID, long control) {
        long nControl = 0;
        if (control == 0) {
            nControl = 0x0FFFFFFFFFL;
        } else if ((control >= 1) && (control <= 3)) {
            // 对应标准的 1~3 项
            nControl |= 0x01L << (control - 1);
        } else if ((control >= 4) && (control <= 7)) {
            // 对应标准的 7~10 项
            nControl |= 0x01L << (control + 3 - 1);
        } else if ((control == 8)) {
            // 对应标准的 13 项
            nControl |= 0x01L << (control + 5 - 1);
        } else if ((control == 11)) {
            // 对应标准的 18 项
            nControl |= 0x01L << (control + 7 - 1);
        }

        LOGGER.info("Control: " + String.format("%010X", nControl));
        SimpleManToken SMToken = new SimpleManToken(BigInteger.valueOf(nControl));
        return this.getRtnTokenXML(SMToken.StrToken);
    }

    @Override
    public String getLogoffToken(String meterNo, String sgc, int tariffIndex, int keyVersion, int keyExpiredTime, long keyNo, int seqNo) {
        // token = "11"
        return null;
    }

    @Override
    public String getFriendHolidayToken(String meterNo, String sgc, int tariffIndex, int keyVersion, int keyExpiredTime, long keyNo, int seqNo, int holidayMode, String[] holidays) {
        // token = "11"
        return null;
    }

    private String getAnalsyeRtnTokenXML(String tokenBin, int csOffset) {
        String result = "";
        String tokenType = "";
        String balance = "";
        String seqNo = "";
        String clockSetFlag = "0";
        String batteryVoltageLowFlag = "0";
        String openCoverFlag = "0";
        String openBottomCoverFlag = "0";
        String byPassFlag = "0";
        String reverseFlag = "0";
        String magneticInterfereFlag = "0";
        String relayStatusFlag = "0";
        String relayFaultFlag = "0";
        String overdraftUsedFlag = "0";
        String forwardActiveEnergyTol = "0";
        String dataBin = "";
        dataBin = tokenBin.substring(0, 2);
        if (dataBin.compareTo("00") == 0) {
            tokenType = "0";
        } else if (dataBin.compareTo("01") == 0) {
            tokenType = "1";
        }

        dataBin = tokenBin.substring(2, 10);
        seqNo = TokenUtils.BinToDec(dataBin);
        dataBin = tokenBin.substring(10, 20);
        if (String.valueOf(dataBin.charAt(9)).compareTo("1") == 0) {
            clockSetFlag = "1";
        }

        if (String.valueOf(dataBin.charAt(8)).compareTo("1") == 0) {
            batteryVoltageLowFlag = "1";
        }

        if (String.valueOf(dataBin.charAt(7)).compareTo("1") == 0) {
            openCoverFlag = "1";
        }

        if (String.valueOf(dataBin.charAt(6)).compareTo("1") == 0) {
            openBottomCoverFlag = "1";
        }

        if (String.valueOf(dataBin.charAt(5)).compareTo("1") == 0) {
            byPassFlag = "1";
        }

        if (String.valueOf(dataBin.charAt(4)).compareTo("1") == 0) {
            reverseFlag = "1";
        }

        if (String.valueOf(dataBin.charAt(3)).compareTo("1") == 0) {
            magneticInterfereFlag = "1";
        }

        if (String.valueOf(dataBin.charAt(2)).compareTo("1") == 0) {
            relayStatusFlag = "1";
        }

        if (String.valueOf(dataBin.charAt(1)).compareTo("1") == 0) {
            relayFaultFlag = "1";
        }

        if (String.valueOf(dataBin.charAt(0)).compareTo("1") == 0) {
            overdraftUsedFlag = "1";
        }

        dataBin = tokenBin.substring(20, 38);
        balance = TokenUtils.BinToDec(dataBin);
        if (csOffset == this.decimalOffset) {
            balance = Double.toString(Double.parseDouble(balance) / 10.0D);
        }

        dataBin = tokenBin.substring(38, 58);
        forwardActiveEnergyTol = TokenUtils.BinToDec(dataBin);
        if (csOffset == this.decimalOffset) {
            forwardActiveEnergyTol = Double.toString(Double.parseDouble(forwardActiveEnergyTol) / 10.0D);
        }

        result = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
        result = result + "<result>";
        result = result + "<type>" + tokenType + "</type>";
        result = result + "<balance>" + balance + "</balance>";
        result = result + "<sequence>" + seqNo + "</sequence>";
        result = result + "<event>";
        result = result + "<clockSetFlag>" + clockSetFlag + "</clockSetFlag>";
        result = result + "<batteryVoltageLowFlag>" + batteryVoltageLowFlag + "</batteryVoltageLowFlag>";
        result = result + "<openCoverFlag>" + openCoverFlag + "</openCoverFlag>";
        result = result + "<openBottomCoverFlag>" + openBottomCoverFlag + "</openBottomCoverFlag>";
        result = result + "<byPassFlag>" + byPassFlag + "</byPassFlag>";
        result = result + "<reverseFlag>" + reverseFlag + "</reverseFlag>";
        result = result + "<magneticInterfereFlag>" + magneticInterfereFlag + "</magneticInterfereFlag>";
        result = result + "<relayStatusFlag>" + relayStatusFlag + "</relayStatusFlag>";
        result = result + "<relayFaultFlag>" + relayFaultFlag + "</relayFaultFlag>";
        result = result + "<overdraftUsedFlag>" + overdraftUsedFlag + "</overdraftUsedFlag>";
        result = result + "<forwardActiveEnergyTol>" + forwardActiveEnergyTol + "</forwardActiveEnergyTol>";
        result = result + "<tariffIndex>" + "0" + "</tariffIndex>";
        result = result + "</event>";
        result = result + "</result>";
        return result;
    }

    @Override
    public String getReturnToken(String meterNo, String sgc, int tariffIndex, int keyVersion, int keyExpiredTime, long keyNo, String Token) {
        String tokenData = "";
        String tokenBin = "";
        String decodeKey = "";
        int csOffset = 0;
        int res = this.CheckInputParams(meterNo, tariffIndex, keyVersion, keyExpiredTime, keyNo, 1, 1);
        if (res != 0) {
            return this.getRtnErrorXML(res);
        } else {
            decodeKey = TokenUtils.getDecoderKey(meterNo, Integer.toString(tariffIndex), sgc, this.vendingKey, this.keyType, Integer.toString(keyVersion));
            tokenBin = TokenUtils.BigIntToBin(Token, 66);
            char char_35 = tokenBin.charAt(0);
            char char_36 = tokenBin.charAt(1);
            tokenBin = tokenBin.substring(2, tokenBin.length());
            char char_00 = tokenBin.charAt(35);
            char char_01 = tokenBin.charAt(36);
            tokenBin = tokenBin.substring(0, 35) + String.valueOf(char_35) + char_36 + tokenBin.substring(37, 64);
            tokenData = TokenUtils.BinToHex(tokenBin, 16);
            tokenData = TokenUtils.FDecryptionAlgorithm07(tokenData, decodeKey);
            tokenBin = TokenUtils.HexToBin(tokenData, 64);
            String originalTokenBin = String.valueOf(char_00) + String.valueOf(char_01) + tokenBin.substring(0, 56);
            if (TokenUtils.getRtnTokenCheckSum(originalTokenBin, this.decimalOffset).compareTo(tokenBin.substring(56, 64)) == 0) {
                csOffset = this.decimalOffset;
            } else {
                if (TokenUtils.getRtnTokenCheckSum(originalTokenBin, this.NodecimalOffset).compareTo(tokenBin.substring(56, 64)) != 0) {
                    return "";
                }

                csOffset = this.NodecimalOffset;
            }

            String rtnTokenXML = this.getAnalsyeRtnTokenXML(originalTokenBin, csOffset);
            return rtnTokenXML;
        }
    }

    @Override
    public String encryptSmartCardReturnData(int answerToReset, int binaryPattern, int version, String meterID, String customerID, String utilityID, long sanctionedLoad, int meterType, int lastRechargeAmount, String lastRechargeDate, String lastTransactionID, String cardType, String pwdData, int TokenTotalNumber, String[] tokens) {
        // 私有的功能
        return null;
    }

    @Override
    public String decryptSmartCardReturnData(String meterNo, String sgc, int tariffIndex, int keyVersion, int keyExpiredTime, long keyNo, String encryptedData) {
        // 私有的功能
        return null;
    }

    private int CheckInputParams(String meterNo, int tarrifIndex, int keyVersion, int keyExpiredTime, long keyNo, int seqNo, int amount) {
        if (seqNo >= 0 && seqNo <= this.maxSeqNo) {
            if (tarrifIndex >= 0 && tarrifIndex <= 99) {
                if (keyVersion >= 0 && keyVersion <= 9) {
                    if (keyExpiredTime >= 0 && keyExpiredTime <= 255) {
                        if (keyNo >= 0L && keyNo <= 65535L) {
                            if (meterNo != null && meterNo.length() > 0 && meterNo.length() <= 16) {
                                return amount >= 1 && amount <= 99999999 ? 0 : 7;
                            } else {
                                return 6;
                            }
                        } else {
                            return 5;
                        }
                    } else {
                        return 4;
                    }
                } else {
                    return 3;
                }
            } else {
                return 2;
            }
        } else {
            return 1;
        }
    }

    private String getRtnErrorXML(int error) {
        String result = "";
        result = result + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
        result = result + "<result>";
        result = result + "<errorCode>" + error + "</errorCode>";
        result = result + "</result>";
        return result;
    }

    private String getRtnTokenXML(String tokenList) {
        String result = "";
        String token = "";
        if (tokenList.charAt(0) == ',') {
            tokenList = tokenList.substring(1, tokenList.length());
        }

        result = result + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
        result = result + "<result>";
        result = result + "<errorCode>0</errorCode>";

        Integer pos;
        for(result = result + "<tokens>"; tokenList.indexOf(",") > -1; tokenList = tokenList.substring(pos + 1, tokenList.length())) {
            pos = tokenList.indexOf(",");
            token = TokenUtils.substring(tokenList, 0, pos);
            token = this.formatTokenWithSpace(token);
            result = result + "<token>";
            result = result + token;
            result = result + "</token>";
        }

        token = this.formatTokenWithSpace(tokenList);
        result = result + "<token>";
        result = result + token;
        result = result + "</token>";
        result = result + "</tokens>";
        result = result + "</result>";
        return result;
    }

    private String formatTokenWithSpace(String token) {
        String result = "";
        token = token.replace(" ", "");
        result = TokenUtils.substring(token, 0, 4) + " ";
        result = result + TokenUtils.substring(token, 4, 4) + " ";
        result = result + TokenUtils.substring(token, 8, 4) + " ";
        result = result + TokenUtils.substring(token, 12, 4) + " ";
        result = result + TokenUtils.substring(token, 16, 4);
        return result;
    }

}
